/*
 * To change this template, choose Tools | Templates and open the template in
 * the editor.
 */
package abricots.game;

import abricots.MyLog;
import abricots.entity.Entity;
import abricots.entity.RootEntity;
import abricots.entity.state.EntityState;
import com.esotericsoftware.minlog.Log;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import org.newdawn.slick.geom.Shape;

/**
 *
 * @author charly
 */
public class EntitiesManager {

    private final MyLog log = new MyLog(getClass().getSimpleName(), Log.LEVEL_DEBUG);
    private final Map<Integer, Entity> entities;
    private final Map<Integer, Entity> localEntities;
    private final List<Long> lastCheckedCollisions;
    private final GameEnv env;
    private RootEntity rootEntity;
    private int lastLocalId; //TODO: safe way of handling local entities

    public EntitiesManager(GameEnv env) {
        this.env = env;
        entities = Collections.synchronizedMap(new HashMap<Integer, Entity>());
        localEntities = Collections.synchronizedMap(new HashMap<Integer, Entity>());
        lastCheckedCollisions = new ArrayList<Long>();
        rootEntity = new RootEntity();
        rootEntity.setValidated(true);
        rootEntity.setId(0);
        rootEntity.register(env);
        lastLocalId = 0;
    }

    public RootEntity getRootEntity() {
        return rootEntity;
    }

    public void checkCollisions(long time) {
        if (lastCheckedCollisions.contains(time)) {
            return;
        }
        List<Integer> entitiesId = new ArrayList<Integer>();
        synchronized (entities) {
            entitiesId.addAll(entities.keySet());
        }
        synchronized (localEntities) {
            entitiesId.addAll(localEntities.keySet());
        }
        lastCheckedCollisions.add(time);
        log.trace("Checking all collision for frame " + time);
        for (int idx1 = 0; idx1 < entitiesId.size(); idx1++) {
            int id1 = entitiesId.get(idx1);
            Entity entity1 = getEntity(id1);
            if (entity1 == null) {
                log.warn("Cannot find entity " + id1);
                continue;
            }
            EntityState state1 = entity1.getStateManager().getEntityState(time);
            if (state1 == null) {
                log.warn("No state found for collision check at " + time + " for entity " + entity1.toString());
                continue;
            }
            Shape shape1 = entity1.getCollisionManager().getCollisionShape(state1);
            if (shape1 == null) {
                log.warn("No collision shape for entity " + entity1.toString());
                continue;
            }

            for (int idx2 = idx1 + 1; idx2 < entitiesId.size(); idx2++) {
                int id2 = entitiesId.get(idx2);
                Entity entity2 = getEntity(id2);
                if (entity2 == null) {
                    log.warn("Cannot find entity " + id2);
                    continue;
                }
                EntityState state2 = entity2.getStateManager().getEntityState(time);
                if (state2 == null) {
                    log.warn("No state found for collision check at " + time + " for entity " + entity2.toString());
                    continue;
                }
                if (!entity1.getCollisionManager().isCollisionCheckNeeded(entity2)) {
                    continue;
                }
//                log.trace(" - Checking " + entity1 + " vs. " + entity2);
                Shape shape2 = entity2.getCollisionManager().getCollisionShape(state2);
                if (shape2 == null) {
                    log.warn("No collision shape for " + entity2.toString());
                    continue;
                }
                if (shape1.intersects(shape2)) {
                    log.trace("collision for " + time + " with entity " + entity1.getId() + " and " + entity2.getId());
                    entity1.getCollisionManager().onCollision(entity2, time);
                    entity2.getCollisionManager().onCollision(entity1, time);
                }
                //TODO: use lines from lastpos to actual pos and check if it crosses other shape/line?
            }
        }
    }

    public void addEntity(Entity entity) {
        int localId = ++lastLocalId;
        entity.setId(localId);
        log.debug(getClass().getSimpleName() + " Adding new Entity " + entity.getId() + " " + entity.toString());
        localEntities.put(localId, entity);

    }

    public void removeEntity(int entityId, boolean local) {
        // Double check if we remove the right entity as different entities
        // might have the same ids , one in each list
        log.debug("Removing Entity " + entityId);
        if (local) {
            synchronized (localEntities) {
                if (localEntities.containsKey(entityId)) {
                    localEntities.remove(entityId);
                } else {
                    log.debug("Asked to remove unknown local entity : " + entityId);
                }
            }
            return;
        }
        synchronized (entities) {
            if (entities.containsKey(entityId)) {
                entities.remove(entityId);
            } else {
                log.debug("Asked to remove unknown entity : " + entityId);
            }
        }
    }

    public void removeEntity(int entityId) {
        removeEntity(entityId, false);
    }

    public boolean validateEntity(int localId, int id) {
        log.trace("Validating entity " + localId + " to " + id);
        if (containsEntity(id)) {
            return true;
        }
        Entity entity = null;
        synchronized (localEntities) {
            entity = localEntities.remove(localId);
        }
        if (entity == null) {
            return false;
        }
        entity.setId(id);
        entity.setValidated(true);
        synchronized (entities) {
            entities.put(entity.getId(), entity);
        }
        rootEntity.addChild(localId);
        return true;
    }

    public boolean containsEntity(int id) {
        return entities.containsKey(id);
    }

    public Collection<Entity> getLocalEntities() {
        return localEntities.values();
    }

    public Entity getEntity(int id) {
        if (isLocalEntity(id)) {
            return localEntities.get(id);
        }
        return entities.get(id);
    }

    public boolean isLocalEntity(int id) {
        synchronized (entities) {
            if (!entities.containsKey(id) && localEntities.containsKey(id)) {
                return true;
            }
        }
        return false;
    }

    public void cleanUp(long lastNeeded, long lastValid) {
        synchronized (entities) {
            for (Entity entity : entities.values()) {
                entity.cleanUp(lastNeeded, lastValid);
            }
        }
        if (lastNeeded >= 0) {
            Collections.sort(lastCheckedCollisions);
            Iterator<Long> it = lastCheckedCollisions.iterator();
            while (it.hasNext()) {
                long time = it.next();
                if (time < lastNeeded) {
                    it.remove();
                }
                break;
            }
        }
        if (lastValid >= 0) {
            Collections.sort(lastCheckedCollisions);
            Iterator<Long> it = lastCheckedCollisions.iterator();
            while (it.hasNext()) {
                long time = it.next();
                if (time <= lastNeeded) {
                    continue;
                }
                it.remove();
            }
        }
    }
}
